#include <sys/socket.h>
#include <arpa/inet.h>
#include <error.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <mysql/mysql.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <jwt.h>
#include <cjson/cJSON.h>

#include "include/connection.h"
#include "include/tlv.h"
#include "include/mysql.h"
#include "include/error.h"
#include "include/cqueue.h"
#include "include/vds.h"
#include "include/config.h"
#include "include/mysql_log.h"

#define QUERY_MAX_SIZE          4096
#define USERNAME_MAX_LENGTH     30
#define ENCRYPTED_MAX_LENGTH    100
#define TOKEN_MAX_LENGTH        512
#define PATH_MAX_LENGTH         1024
#define SHA1_MAX_LENGTH         100
#define FILENAME_MAX_LENGTH     100

/* const char host[]="localhost"; */
/* const char user[]="root"; */
/* const char passwd[]="linhai0809"; */
/* const char db[]="netdisk_demo"; */
/* const unsigned int port=3306; */

const char *default_upload_server;
const char *secret="uia89SH87hjakgHJJbh8";

void generate_salt(char *salt,size_t length){

    unsigned char raw_salt[16];
    char b64_salt[24] = {0}; // Base64 编码长度约为 22
    const char *b64_chars =
        "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    int fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0 || read(fd, raw_salt, 16) != 16) {
        thread_perror("rand read");
        return ;
    }
    close(fd);

    // 手动 base64 编码（crypt 风格）
    for (int i = 0, j = 0; i < 15; i += 3) {
        uint32_t val = (raw_salt[i] << 16) | (raw_salt[i+1] << 8) | raw_salt[i+2];
        b64_salt[j++] = b64_chars[(val >> 18) & 0x3f];
        b64_salt[j++] = b64_chars[(val >> 12) & 0x3f];
        b64_salt[j++] = b64_chars[(val >> 6) & 0x3f];
        b64_salt[j++] = b64_chars[val & 0x3f];
    }

    // 构造盐字符串
    snprintf(salt, length,"$y$j9T$%.22s", b64_salt);
}


int tcp_init(const char *ip,unsigned short port);

typedef void (*callback_token_resolved_success_t)(const char *username,void *arg);
typedef void (*callback_token_resolved_fail_t)(void *arg);
typedef void (*callback_token_expired_t)(const char *username,void *arg);

typedef struct{
    int clientfd;
    MYSQL *mysql;
}account_ctx_t;

void token_invalid(void *arg){
    account_ctx_t *ctx=(account_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    if(tlv_response(clientfd,RESPONSE_INVALID_TOKEN,0,NULL)==-1){thread_perror("tlv_response");}
}

void token_expired(const char *username,void *arg){
    account_ctx_t *ctx=(account_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    if(tlv_response(clientfd,RESPONSE_TOKEN_EXPIRED,0,NULL)==-1){thread_perror("tlv_response");}
}

void callback_deregister(const char *username,void *arg){
    account_ctx_t *ctx=(account_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    MYSQL *mysql=ctx->mysql;
    char query[QUERY_MAX_SIZE];
    snprintf(query,sizeof(query),"UPDATE user SET is_delete=1 WHERE username='%.*s';",USERNAME_MAX_LENGTH,username);
    if(mysql_query(mysql,query)!=0){
        thread_perror_mysql(mysql,"deregister|update");
        if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
    }else{
        if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){thread_perror("tlv_response");}
    }

}

typedef struct{
    int clientfd;
    MYSQL *mysql;
    const char *path;
    const char *filename;
}vds_ctx_t;

void callback_cd(const char *username,void *arg){
    vds_ctx_t *ctx=(vds_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    MYSQL *mysql=ctx->mysql;
    const char *path=ctx->path;
    if(cmd_cd(mysql,username,path)==0){
        if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){thread_perror("tlv_response");}
    }else{
        thread_perror_mysql(mysql,"cmd_cd");
        if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
    }
}

void callback_ls(const char *username,void *arg){
    vds_ctx_t *ctx=(vds_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    MYSQL *mysql=ctx->mysql;
    const char *path=ctx->path;
    char output[4096];
    if(cmd_ls(mysql,username,path,output)==0){
        if(tlv_response(clientfd,RESPONSE_OK,strlen(output)+1,(const uint8_t*)output)==-1){thread_perror("tlv_response");}
    }else{
        thread_perror_mysql(mysql,"cmd_ls");
        if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
    }
}

void callback_mkdir(const char *username,void *arg){
    vds_ctx_t *ctx=(vds_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    MYSQL *mysql=ctx->mysql;
    const char *path=ctx->path;
    if(cmd_mkdir(mysql,username,path)==0){
        if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){thread_perror("tlv_response");}
    }else{
        thread_perror_mysql(mysql,"cmd_mkdir");
        if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
    }
}

void callback_rmdir(const char *username,void *arg){
    vds_ctx_t *ctx=(vds_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    MYSQL *mysql=ctx->mysql;
    const char *path=ctx->path;
    if(cmd_rmdir(mysql,username,path)==0){
        if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){thread_perror("tlv_response");}
    }else{
        thread_perror_mysql(mysql,"cmd_rmdir");
        if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
    }
}

void callback_remove(const char *username,void *arg){
    vds_ctx_t *ctx=(vds_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    MYSQL *mysql=ctx->mysql;
    const char *path=ctx->path;
    if(cmd_remove(mysql,username,path)==0){
        if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){thread_perror("tlv_response");}
    }else{
        thread_perror_mysql(mysql,"cmd_remove");
        if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
    }
}

typedef struct{
    int clientfd;
    MYSQL *mysql;
    const char *sha1;
}puts_ctx_t;

void callback_puts(const char *username,void *arg){
    puts_ctx_t *ctx=(puts_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    MYSQL *mysql=ctx->mysql;
    const char *sha1=ctx->sha1;
    char query[4096];
    snprintf(query,sizeof(query),"SELECT id FROM files WHERE sha1='%.*s';",SHA1_MAX_LENGTH,sha1);
    if(mysql_query(mysql,query)!=0){
        thread_perror_mysql(mysql,"puts|select");
        if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
    }else{
        MYSQL_RES *res=mysql_store_result(mysql);
        size_t row_num=mysql_num_rows(res);
        mysql_free_result(res);
        if(row_num>0){
            if(tlv_response(clientfd,RESPONSE_FILE_EXIST,0,NULL)==-1){thread_perror("tlv_response");}
        }else{
            // default upload server
            char buf[]="1 ";
            strcat(buf,default_upload_server);
            if(tlv_response(clientfd,RESPONSE_OK,strlen(buf)+1,(const uint8_t*)buf)==-1){thread_perror("tlv_response");}
        }
    }
}

typedef struct{
    int clientfd;
    MYSQL *mysql;
    const char *sha1;
    const char *path;
    const char *filename;
    size_t file_size;
}puts_res_ctx_t;

void callback_puts_result(const char *username,void *arg){
    puts_res_ctx_t *ctx=(puts_res_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    MYSQL *mysql=ctx->mysql;
    const char *sha1=ctx->sha1;
    const char *path=ctx->path;
    const char *filename=ctx->filename;
    size_t file_size=ctx->file_size;
    char query[QUERY_MAX_SIZE];
    
    snprintf(query,sizeof(query),"INSERT INTO files (sha1,file_size ,server) VALUES ('%.*s',%lu, JSON_ARRAY('%s'));",SHA1_MAX_LENGTH,sha1,file_size,default_upload_server);
    
    if(mysql_query(mysql,query)!=0){
        thread_perror_mysql(mysql,"puts|insert into");
    }

    snprintf(query,sizeof(query),
            " INSERT INTO user_files (user_id, file_id, type, path, filename) "
            " VALUES ( "
            " (SELECT id FROM user WHERE username = '%.*s'), "
            " (SELECT id FROM files WHERE sha1 = '%.*s'), "
            " 'f','%.*s','%.*s'); ",
            USERNAME_MAX_LENGTH,username,
            SHA1_MAX_LENGTH,sha1,
            PATH_MAX_LENGTH,path,
            FILENAME_MAX_LENGTH,filename);

    if(mysql_query(mysql,query)!=0){
        thread_perror_mysql(mysql,"puts|insert into");
        if(mysql_errno(mysql)==1062){
            if(tlv_response(clientfd,RESPONSE_FILE_EXIST,0,NULL)==-1){thread_perror("tlv_response");}
        }else{
            if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
        }
    }else{
        if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){thread_perror("tlv_response");}
    }
}

typedef struct{
    int clientfd;
    MYSQL *mysql;
    const char *path;
    const char *filename;
}gets_ctx_t;

void callback_gets(const char *username,void *arg){
    gets_ctx_t *ctx=(gets_ctx_t*)arg;
    int clientfd=ctx->clientfd;
    MYSQL *mysql=ctx->mysql;
    const char *path=ctx->path;
    const char *filename=ctx->filename;
    char query[QUERY_MAX_SIZE];
    snprintf(query,sizeof(query),
             " SELECT sha1,file_size,server FROM files "
             " WHERE "
             " id=(SELECT file_id FROM user_files "
             " WHERE user_id=(SELECT id FROM user WHERE username='%.*s') AND path='%.*s' AND filename='%.*s'); "
             ,USERNAME_MAX_LENGTH,username,
             PATH_MAX_LENGTH,path,
             FILENAME_MAX_LENGTH,filename);

    if(mysql_query(mysql,query)!=0){
        thread_perror_mysql(mysql,"gets|select");
        if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
    }else{
        MYSQL_RES *res=mysql_store_result(mysql);
        int row_num=mysql_num_rows(res);
        if(row_num<=0){
            if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
        }else{
            MYSQL_ROW row=mysql_fetch_row(res);
            const char *sha1=row[0];
            char *endptr;
            size_t file_size=strtoul(row[1],&endptr,10);
            printf("sha1=%s\nfile_size=%lu,server=%s\n",sha1,file_size,row[2]);
            if(*endptr!='\0'){
                thread_perror("strtoul");
                if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
            }else{
                cJSON *array = cJSON_Parse(row[2]);
                size_t server_count = cJSON_GetArraySize(array);
                if(server_count<=0){
                    // no server contains this file
                    tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL);
                }else{
                    char buf[4096];
                    off_t buf_offset=snprintf(buf,sizeof(buf),"%s %lu ",sha1,server_count);
                    size_t chunk_size=file_size/server_count;
                    off_t offset=0;
                    for(size_t i=0;i<server_count-1;i++){
                        snprintf(buf+buf_offset,sizeof(buf)-buf_offset,"%s %ld %lu ",cJSON_GetArrayItem(array,i)->valuestring,offset,chunk_size);
                        offset+=chunk_size;
                    }
                    snprintf(buf+buf_offset,sizeof(buf)-buf_offset,"%s %ld %lu ",cJSON_GetArrayItem(array,server_count-1)->valuestring,offset,file_size-offset);

                    cJSON_Delete(array);

                    tlv_response(clientfd,RESPONSE_OK,strlen(buf)+1,(const uint8_t *)buf);
                }
            }
        }
        mysql_free_result(res);
    }
}


void resolve_token(const char*token,void *arg,callback_token_resolved_success_t callback_success,callback_token_expired_t callback_expired,callback_token_resolved_fail_t callback_fail);

void close_connection(int epfd,connection_t *connection){
    int clientfd=connection->clientfd;
    epoll_ctl(epfd,EPOLL_CTL_DEL,clientfd,NULL);
    d_link_node_destory(connection->node);
    connection_destory(connection);
    close(clientfd);
}

int main(int argc, char *argv[])
{
    // ./server server.conf
    config_t cfg;
    bzero(&cfg,sizeof(cfg));
    mysql_config_t mysql_cfg;
    bzero(&mysql_cfg,sizeof(mysql_cfg));
    const char *config_path;
    if(argc==1){
        config_path="./server.conf";
    }else if(argc==2){
        config_path=argv[1];
    }else{
        fprintf(stderr,"ARGS NUM IS WRONG.\n");
        exit(-1);
    }

    if(load_config(config_path,&cfg,&mysql_cfg)!=0){
        perror("load_config error");
        exit(-1);
    }

    if(strlen(cfg.secret)>0){
        secret=cfg.secret;
    }

    default_upload_server=cfg.default_upload_server;

    MYSQL *mysql=mysql_init(NULL);
    mysql_log(mysql,LOG_INFO,"the initialization of the database has been completed");
    if(mysql==NULL){thread_perror("mysql_init");exit(-1);}
    if(mysql_real_connect(mysql,mysql_cfg.host,mysql_cfg.user,mysql_cfg.passwd,mysql_cfg.db,mysql_cfg.port,NULL,0)==NULL){thread_perror_mysql(mysql,"mysql_real_connect");exit(-1);};


    // 初始化sockfd
    int sockfd;
    if((sockfd=tcp_init(cfg.ip,(unsigned short)cfg.port))==-1){thread_perror("tcp init");exit(-1);}
    mysql_log(mysql,LOG_INFO,"listen ip=%s port=%ld",cfg.ip,cfg.port);

    // 创建epoll
    int epfd;
    if((epfd=epoll_create(1))==-1){thread_perror("epoll_create");exit(-1);}

    // 监听sockfd
    {
        connection_t *connection=connection_create(sockfd);
        if(connection==NULL){thread_perror("connection_create");exit(-1);}
        //mysql_log(mysql,LOG_INFO,"socket:%d is connected",sockfd);
        struct epoll_event ev;
        ev.events=EPOLLIN;
        ev.data.ptr=connection;
        if(epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev)==-1){thread_perror("epoll_add");exit(-1);}
        mysql_log(mysql,LOG_INFO,"epfd:%d is listening sockfd:%d",epfd,sockfd);
    }

    // 创建1s的定时器
    int tfd=timerfd_create(CLOCK_MONOTONIC,0);
    if(tfd==-1){thread_perror("timerfd_create");exit(-1);}
    struct itimerspec timer;
    timer.it_value.tv_sec=1;
    timer.it_value.tv_nsec=0;
    timer.it_interval.tv_sec=1;
    timer.it_interval.tv_nsec=0;
    mysql_log(mysql,LOG_INFO,"tfd:%d have been created",tfd);
    if(timerfd_settime(tfd,0,&timer,NULL)==-1){thread_perror("timerfd_settime");exit(-1);}

    // 监听timerfd
    {
        connection_t *connection=connection_create(tfd);
        if(connection==NULL){thread_perror("connection_create");exit(-1);}
    
        struct epoll_event ev;
        ev.events=EPOLLIN;
        ev.data.ptr=connection;
        if(epoll_ctl(epfd,EPOLL_CTL_ADD,tfd,&ev)==-1){thread_perror("epoll_add");exit(-1);}
        mysql_log(mysql,LOG_INFO,"tfd:%d have been listening by epfd:%d",tfd,epfd);
    }

    cqueue_t *cqueue=cqueue_create(30);
    mysql_log(mysql,LOG_INFO,"circular queue have been created");
    struct epoll_event evs[1024];
    while(1){
        int ready_num=epoll_wait(epfd,evs,sizeof(evs),0);
        for(int i=0;i<ready_num;i++){
            connection_t *connection=(connection_t*)evs[i].data.ptr;
            if(connection->clientfd==sockfd){
                int clientfd = accept(sockfd,NULL,NULL);
                mysql_log(mysql,LOG_INFO,"clientfd:%d have been connected",clientfd);
                connection_t *connection=connection_create(clientfd);
                if(connection==NULL){thread_perror("connection_create");close(clientfd);continue;}

                struct epoll_event ev;
                ev.events=EPOLLIN;
                ev.data.ptr=connection;
                if(epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev)==-1){thread_perror("epoll_add");connection_destory(connection);close(clientfd);continue;}
                mysql_log(mysql,LOG_INFO,"clientfd:%d have been listening by epfd:%d",clientfd,epfd);
                connection->node=d_link_node_create(connection);
                cqueue_move_to_rear(cqueue,connection->node);

                printf("clientfd %d is listened.\n",clientfd);
            }else if(connection->clientfd==tfd){
                uint64_t expirations;
                if(read(tfd,&expirations,sizeof(expirations))!=sizeof(expirations)){thread_perror("timer fd read");continue;}

                d_list_t *d_list=cqueue_front(cqueue);
                for(d_link_node_t *node=d_list->dummy_head->next;node!=d_list->dummy_tail;){
                    d_link_node_t *tmp_node=node;
                    node=node->next;
                    
                    connection_t *connection=(connection_t*)tmp_node->ptr;
                    if(connection->status==connection_active){continue;}
                    
                    int clientfd = connection->clientfd;

                    close_connection(epfd,connection);
                    mysql_log(mysql,LOG_INFO,"clientfd:%d is kicked out",clientfd);
                    printf("clientfd=%d is kicked out.\n",clientfd);
                }
                cqueue_step_next(cqueue);
            }else{
                cqueue_move_to_rear(cqueue,connection->node);

                int clientfd=connection->clientfd;

                uint32_t type;
                uint32_t length;
                uint8_t value[TLV_VALUE_MAX_SIZE];

                if(tlv_listen(clientfd,&type,&length,value)==-1){
                    if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){
                        // ...
                        thread_perror("tlv_response");
                    }
                    // ...
                    thread_perror("tlv_listen");
                    close_connection(epfd,connection);
                    printf("clientfd %d is shutdown.\n",clientfd);
                    mysql_log(mysql,LOG_INFO,"clientfd:%d is shutdown.",clientfd);
                    continue;
                }

                printf("type=%d\nlength=%d\nvalue=%s\n",type,length,value);
                switch(type){
                case REQUEST_EXIT:
                    {
                        if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_EXIT");thread_perror("tlv_response");}
                        else{
                            mysql_log(mysql,LOG_INFO,"epfd:%d is closing.",epfd);
                            close_connection(epfd,connection);
                        }
                        break;
                    }
                case REQUEST_GET_SALT:
                    {
                        const char *username=(const char*)value;
                        char query[QUERY_MAX_SIZE+1];

                        snprintf(query,sizeof(query),"SELECT encrypted FROM user WHERE username='%.*s' AND is_delete = 0;",USERNAME_MAX_LENGTH,username);
                        mysql_log(mysql,LOG_INFO,"a query operation was performed:SELECT encrypted FROM user WHERE username='%.*s' AND is_delete = 0;",USERNAME_MAX_LENGTH,username);
                        ssize_t ret=mysql_query(mysql,query);
                        if(ret!=0){
                            thread_perror_mysql(mysql,"get salt select");
                            if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_GET_SALT");thread_perror("tlv_response");}
                        }else{
                            MYSQL_RES *res=mysql_store_result(mysql);
                            int row_num=mysql_num_rows(res);

                            char salt[64];
                            if(row_num==0){
                                // 用户名不存在,或者已注销，生成随机盐值
                                generate_salt(salt,sizeof(salt));
                                if(tlv_response(clientfd,RESPONSE_OK,strlen(salt)+1,(const uint8_t*)salt)==-1){thread_perror("tlv_response");}
                            }else{
                                // 用户名存在，切割出盐值
                                MYSQL_ROW row=mysql_fetch_row(res);
                                const char *encrypted=row[0];

                                char *p=(char*)encrypted;
                                int dollar_count=0;
                                while(dollar_count<4){
                                    if(*p++=='$'){
                                        dollar_count++;
                                    }
                                }

                                memcpy(salt,encrypted,p-encrypted);
                                salt[p-encrypted]='\0';
                                if(tlv_response(clientfd,RESPONSE_OK,strlen(salt)+1,(const uint8_t*)salt)==-1){thread_perror("tlv_response");}
                            }
                            mysql_log(mysql,LOG_INFO,"reply to the client with the salt value of a user");
                            mysql_free_result(res);
                        }

                        break;
                    }
                case REQUEST_REGISTER:
                    {
                        char query[QUERY_MAX_SIZE];
                        char username[USERNAME_LENGTH+1];
                        char pwd_encrypted[ENCRYPTED_MAX_LENGTH+1];

                        sscanf((const char*)value,"%s %s",username,pwd_encrypted);

                        snprintf(query,sizeof(query),"SELECT encrypted,is_delete FROM user WHERE username='%.*s';",USERNAME_MAX_LENGTH,username);
                        mysql_log(mysql,LOG_INFO,"a query operation was performed:SELECT encrypted,is_delete FROM user WHERE username='%.*s';",USERNAME_MAX_LENGTH,username);
                        ssize_t ret=mysql_query(mysql,query);
                        if(ret!=0){
                            thread_perror_mysql(mysql,"register select");
                            if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_REGISTER");thread_perror("tlv_response");}
                        }else{
                            MYSQL_RES *res=mysql_store_result(mysql);
                            int row_num=mysql_num_rows(res);
                            if(row_num==0){
                                // 用户表未添加过，直接添加表项
                                snprintf(query,sizeof(query),"INSERT INTO user (username,encrypted,is_delete) VALUES('%.*s','%.*s',0);",USERNAME_MAX_LENGTH,username,ENCRYPTED_MAX_LENGTH,pwd_encrypted);
                                mysql_log(mysql,LOG_INFO,"a query operation was performed:INSERT INTO user (username,encrypted,is_delete) VALUES('%.*s','%.*s',0);",USERNAME_MAX_LENGTH,username,ENCRYPTED_MAX_LENGTH,pwd_encrypted);
                                if(mysql_query(mysql,query)!=0){
                                    thread_perror_mysql(mysql,"register insert into");
                                    if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_REGISTER");thread_perror("tlv_response");}
                                }else{
                                    if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_REGISTER");thread_perror("tlv_response");}
                                }
                            }else{
                                MYSQL_ROW row = mysql_fetch_row(res);
                                if(row[1][0]=='0'){
                                    // 用户名添加过，且未注销
                                    if(tlv_response(clientfd,RESPONSE_USERNAME_IS_EXIST,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_REGISTER");thread_perror("tlv_response");}
                                }else{
                                    // 用户名添加过，且已被注销，更新表项
                                    snprintf(query,sizeof(query),"UPDATE user SET encrypted='%.*s',is_delete=0 WHERE username='%.*s';",ENCRYPTED_MAX_LENGTH,pwd_encrypted,USERNAME_MAX_LENGTH,username);
                                    mysql_log(mysql,LOG_INFO,"a query operation was performed:UPDATE user SET encrypted='%.*s',is_delete=0 WHERE username='%.*s';",ENCRYPTED_MAX_LENGTH,pwd_encrypted,USERNAME_MAX_LENGTH,username);
                                    if(mysql_query(mysql,query)!=0){
                                        thread_perror_mysql(mysql,"register update");
                                        mysql_log(mysql,LOG_INFO,"thread_perror in register update");
                                        if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_REGISTER");thread_perror("tlv_response");}
                                    }else{
                                        if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_REGISTER");thread_perror("tlv_response");}
                                    }
                                }
                            }
                            mysql_free_result(res);
                        }
                        break;

                    }
                case REQUEST_LOGIN:
                    {
                        char query[QUERY_MAX_SIZE];
                        char username[USERNAME_MAX_LENGTH+1];
                        char pwd_encrypted[ENCRYPTED_MAX_LENGTH+1];
                        sscanf((const char*)value,"%s %s",username,pwd_encrypted);

                        snprintf(query,sizeof(query),"SELECT encrypted FROM user WHERE username='%.*s' AND is_delete=0;",USERNAME_MAX_LENGTH,username);
                        mysql_log(mysql,LOG_INFO,"a query operation was performed:SELECT encrypted FROM user WHERE username='%.*s' AND is_delete=0;",USERNAME_MAX_LENGTH,username);
                        ssize_t ret=mysql_query(mysql,query);
                        if(ret!=0){
                            mysql_log(mysql,LOG_INFO,"thread_perror in login select");
                            thread_perror_mysql(mysql,"login select");
                            if(tlv_response(clientfd,RESPONSE_ERROR_REQUEST,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_LOGIN");thread_perror("tlv_response");}
                        }else{
                            MYSQL_RES *res=mysql_store_result(mysql);
                            int row_num=mysql_num_rows(res);
                            if(row_num==0){
                                // 用户表不存在该用户，或者该用户已注销
                                if(tlv_response(clientfd,RESPONSE_USERNAME_NOT_EXIST,0,NULL)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_LOGIN");thread_perror("tlv_response");}
                            }else{
                                // 用户表存在该用户，且未注销
                                MYSQL_ROW row = mysql_fetch_row(res);
                                const char *encrypted=row[0];

                                int ret = strcmp(encrypted,pwd_encrypted);
                                if(ret==0){
                                    jwt_t *jwt=NULL;
                                    char *token=NULL;

                                    jwt_new(&jwt);

                                    jwt_set_alg(jwt,JWT_ALG_HS256,(unsigned char*)secret,strlen(secret));

                                    jwt_add_grant(jwt,"username",username);

                                    time_t exp = time(NULL)+3600;
                                    jwt_add_grant_int(jwt,"exp",exp);

                                    token=jwt_encode_str(jwt);

                                    jwt_free(jwt);

                                    if(tlv_response(clientfd,RESPONSE_OK,strlen(token)+1,(const uint8_t*)token)==-1){mysql_log(mysql,LOG_INFO,"tlv_response error in REQUEST_LOGIN");thread_perror("tlv_response");}

                                    free(token);
                                    mysql_log(mysql,LOG_INFO,"token have been relesed");
                                }else{
                                    if(tlv_response(clientfd,RESPONSE_PASSWORD_WRONG,0,NULL)==-1){thread_perror("tlv_response");}
                                }
                            }
                            mysql_free_result(res);
                            mysql_log(mysql,LOG_INFO,"res have been relesed");
                        }
                        break;
                    }
                case REQUEST_DEREGISTER:
                    {
                        const char *token=(const char*)value;
                        account_ctx_t ctx;
                        ctx.clientfd=clientfd;
                        ctx.mysql=mysql;
                        resolve_token(token,&ctx,callback_deregister,token_expired,token_invalid);
                        mysql_log(mysql,LOG_INFO,"user:%d have been deregister",ctx.clientfd);
                        break;
                    }
                case REQUEST_CD:
                    {
                        char token[TOKEN_MAX_LENGTH+1];
                        char path[PATH_MAX_LENGTH+1];
                        sscanf((const char*)value,"%s %s",token,path);
                        vds_ctx_t ctx;
                        ctx.clientfd=clientfd;
                        ctx.mysql=mysql;
                        ctx.path=path;
                        resolve_token(token,&ctx,callback_cd,token_expired,token_invalid);
                        mysql_log(mysql,LOG_INFO,"a cd operation was performed");
                        break;
                    }
                case REQUEST_PWD:
                    {
                        if(tlv_response(clientfd,RESPONSE_OK,0,NULL)==-1){thread_perror("tlv_response");}
                        mysql_log(mysql,LOG_INFO,"a pwd operation was performed");
                        break;
                    }
                case REQUEST_LS:
                    {
                        char token[TOKEN_MAX_LENGTH+1];
                        char path[PATH_MAX_LENGTH+1];
                        sscanf((const char*)value,"%s %s",token,path);
                        vds_ctx_t ctx;
                        ctx.clientfd=clientfd;
                        ctx.mysql=mysql;
                        ctx.path=path;
                        resolve_token(token,&ctx,callback_ls,token_expired,token_invalid);
                        mysql_log(mysql,LOG_INFO,"a ls operation was performed");
                        break;
                    }
                case REQUEST_MKDIR:
                    {
                        char token[TOKEN_MAX_LENGTH+1];
                        char path[PATH_MAX_LENGTH+1];
                        sscanf((const char*)value,"%s %s",token,path);
                        vds_ctx_t ctx;
                        ctx.clientfd=clientfd;
                        ctx.mysql=mysql;
                        ctx.path=path;
                        resolve_token(token,&ctx,callback_mkdir,token_expired,token_invalid);
                        mysql_log(mysql,LOG_INFO,"a mkdir operation was performed");
                        break;
                    }
                case REQUEST_RMDIR:
                    {
                        char token[TOKEN_MAX_LENGTH+1];
                        char path[PATH_MAX_LENGTH+1];
                        sscanf((const char*)value,"%s %s",token,path);
                        vds_ctx_t ctx;
                        ctx.clientfd=clientfd;
                        ctx.mysql=mysql;
                        ctx.path=path;
                        resolve_token(token,&ctx,callback_rmdir,token_expired,token_invalid);
                        mysql_log(mysql,LOG_INFO,"a rmdir operation was performed");
                        break;
                    }
                case REQUEST_PUTS:
                    {
                        connection->status=connection_active;

                        char token[TOKEN_MAX_LENGTH+1];
                        char sha1[SHA1_MAX_LENGTH+1];
                        sscanf((const char*)value,"%s %s",token,sha1);
                        
                        puts_ctx_t ctx;
                        ctx.clientfd=clientfd;
                        ctx.mysql=mysql;
                        ctx.sha1=sha1;
                        resolve_token(token,&ctx,callback_puts,token_expired,token_invalid);
                        mysql_log(mysql,LOG_INFO,"a puts operation was performed");
                        break;
                    }
                case REQUEST_FILE_PUTS_RESULT:
                    {
                        connection->status=connection_idle;
                        char token[512];
                        char sha1[512];
                        char path[512];
                        char filename[512];
                        size_t file_size;
                        sscanf((const char*)value,"%s %s %s %s %lu",token,sha1,path,filename,&file_size);
                        
                        puts_res_ctx_t ctx;
                        ctx.clientfd=clientfd;
                        ctx.mysql=mysql;
                        ctx.sha1=sha1;
                        ctx.path=path;
                        ctx.filename=filename;
                        ctx.file_size=file_size;
                        resolve_token(token,&ctx,callback_puts_result,token_expired,token_invalid);
                        mysql_log(mysql,LOG_INFO,"a file_puts_result operation was performed");
                        break;
                    }
                case REQUEST_GETS:
                    {
                        connection->status=connection_active;

                        char token[TOKEN_MAX_LENGTH+1];
                        char path[SHA1_MAX_LENGTH+1];
                        char filename[FILENAME_MAX_LENGTH+1];
                        sscanf((const char*)value,"%s %s %s",token,path,filename);
                        
                        gets_ctx_t ctx;
                        ctx.clientfd=clientfd;
                        ctx.mysql=mysql;
                        ctx.path=path;
                        ctx.filename=filename;
                        resolve_token(token,&ctx,callback_gets,token_expired,token_invalid);
                        mysql_log(mysql,LOG_INFO,"a gets operation was performed");
                        break;
                    }
                case REQUEST_REMOVE:
                    {
                        char token[TOKEN_MAX_LENGTH+1];
                        char path[PATH_MAX_LENGTH+1];
                        sscanf((const char*)value,"%s %s",token,path);
                        vds_ctx_t ctx;
                        ctx.clientfd=clientfd;
                        ctx.mysql=mysql;
                        ctx.path=path;
                        resolve_token(token,&ctx,callback_remove,token_expired,token_invalid);
                        mysql_log(mysql,LOG_INFO,"a remove operation was performed");
                        break;
                    }
                default:
                    {
                        if(tlv_response(clientfd,RESPONSE_INVALID_REQUEST,0,NULL)==-1){thread_perror("tlv_response");}
                        break;
                    }
                }
            }
        }
    }
    mysql_close(mysql);
    mysql_log(mysql,LOG_INFO,"mysql have been closed");
    close(sockfd);
    mysql_log(mysql,LOG_INFO,"sockfd have been closed");
    return 0;
}

int tcp_init(const char *ip,unsigned short port){
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1){
        perror("socket");
        return -1;
    }

    int flag;
    socklen_t optlen=sizeof(flag);
    if(getsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&flag,&optlen)==-1){
        perror("getsockopt");
        return -1;
    }
    flag=1;
    if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&flag,optlen)==-1){
        perror("setsockopt");
        return -1;
    }

    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_addr.s_addr=inet_addr(ip);
    addr.sin_port=htons(port);

    if(bind(sockfd,(struct sockaddr*)&addr,sizeof(addr))==-1){
        perror("bind");
        return -1;
    }

    if(listen(sockfd,10)==-1){
        perror("listen");
        return -1;
    }

    return sockfd;
}

void resolve_token(const char*token,void *arg,callback_token_resolved_success_t callback_success,callback_token_expired_t callback_expired,callback_token_resolved_fail_t callback_fail){
    jwt_t *jwt=NULL;
    if(jwt_decode(&jwt,token,(unsigned char*)secret,strlen(secret))){
        callback_fail(arg);
    }else{
        const char *username=jwt_get_grant(jwt,"username");
        time_t exp=jwt_get_grant_int(jwt,"exp");
        if(exp<time(NULL)){
            callback_expired(username,arg);
        }else{
            callback_success(username,arg);
        }
        jwt_free(jwt);
    }
}
